package com.xlf.common.redis.service;

import cn.hutool.core.util.BooleanUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import lombok.Data;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.*;
import org.springframework.stereotype.Component;

import java.time.LocalDateTime;
import java.util.*;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.function.BooleanSupplier;
import java.util.function.Consumer;
import java.util.function.Function;

/**
 * @Author xlf 2734029211@qq.com
 * @Description: TODO 常见缓存操作
 * @Version 1.0
 */
@SuppressWarnings(value = { "unchecked", "rawtypes" })
@Component
public class RedisCache
{
    @Autowired
    public RedisTemplate redisTemplate;
//
    @Autowired
    public RedissonClient redissonClient;

    public static final Long CACHE_NULL_TTL = 2L;

    public static final String LOCK_KEY = "lock:";

    private static final ExecutorService CACHE_REBUILD_EXECUTOR = Executors.newFixedThreadPool(10);


    /**
     * 缓存基本的对象，Integer、String、实体类等
     *
     * @param key 缓存的键值
     * @param value 缓存的值
     */
    public <T> void setCacheObject(final String key, final T value)
    {
        redisTemplate.opsForValue().set(key, value);
    }

    /**
     * 缓存基本的对象，Integer、String、实体类等
     *
     * @param key 缓存的键值
     * @param value 缓存的值
     * @param timeout 时间
     * @param timeUnit 时间颗粒度
     */
    public <T> void setCacheObject(final String key, final T value, final Integer timeout, final TimeUnit timeUnit)
    {
        redisTemplate.opsForValue().set(key, value, timeout, timeUnit);
    }

    /**
     * 设置有效时间
     *
     * @param key Redis键
     * @param timeout 超时时间
     * @return true=设置成功；false=设置失败
     */
    public boolean expire(final String key, final long timeout)
    {
        return expire(key, timeout, TimeUnit.SECONDS);
    }

    /**
     * 设置有效时间
     *
     * @param key Redis键
     * @param timeout 超时时间
     * @param unit 时间单位
     * @return true=设置成功；false=设置失败
     */
    public boolean expire(final String key, final long timeout, final TimeUnit unit)
    {

        return redisTemplate.expire(key, timeout, unit);
    }

    /**
     * 获得缓存的基本对象。
     *
     * @param key 缓存键值
     * @return 缓存键值对应的数据
     */
    public <T> T getCacheObject(final String key)
    {
        ValueOperations<String, T> operation = redisTemplate.opsForValue();
        return operation.get(key);
    }

    /**
     * string的key自增
     * @param key 缓存键值对应的数据
     * @return 当前数量
     */
    public Long increment(final String key){
        Long increment = redisTemplate.opsForValue().increment(key);
        return increment;
    }

    /**
     * 如果不存在就设置。
     * @param key
     * @param value
     * @param time
     * @param timeUnit
     * @return 存在就设置失败返回false
     */
    public Boolean setIfAbsent(String key,Object value,long time,TimeUnit timeUnit){
        return redisTemplate.opsForValue().setIfAbsent(key,value,time,timeUnit);
    }

    public Boolean setIfAbsent(String key,Object value){
        return redisTemplate.opsForValue().setIfAbsent(key,value);
    }


    /**
     * 删除单个对象
     *
     * @param key
     */
    public boolean deleteObject(final String key)
    {
        return redisTemplate.delete(key);
    }

    /**
     * 删除集合对象
     *
     * @param collection 多个对象
     * @return
     */
    public long deleteObject(final Collection collection)
    {
        return redisTemplate.delete(collection);
    }


//    -------------------------list-----------------
    /**
     * 缓存List数据
     *
     * @param key 缓存的键值
     * @param dataList 待缓存的List数据
     * @return 缓存的对象
     */
    public <T> long setCacheList(final String key, final List<T> dataList)
    {
        Long count = redisTemplate.opsForList().rightPushAll(key, dataList);
        return count == null ? 0 : count;
    }

    /**
     * 缓存List数据
     *
     * @param key 缓存的键值
     * @param data
     * @return
     */
    public <T> long addCacheList(final String key, final T data)
    {
        Long count = redisTemplate.opsForList().rightPush(key, data);
        return count == null ? 0 : count;
    }

    /**
     * 获得缓存的list对象
     *
     * @param key 缓存的键值
     * @return 缓存键值对应的数据
     */
    public <T> List<T> getCacheList(final String key)
    {
        return redisTemplate.opsForList().range(key, 0, -1);
    }



    //----------------------set-----------------------------------------
    /**
     * 缓存多个Set
     *
     * @param key 缓存键值
     * @param dataSet 缓存的数据
     * @return 缓存数据的对象
     */
    public <T> BoundSetOperations<String, T> setCacheSet(final String key, final Set<T> dataSet)
    {

        BoundSetOperations<String, T> setOperation = redisTemplate.boundSetOps(key);
        Iterator<T> it = dataSet.iterator();
        while (it.hasNext())
        {
            setOperation.add(it.next());
        }
        return setOperation;
    }

    /**
     * 缓存单个Set
     */
    public Long setCacheSet(final String key, final Object value)
    {
        BoundSetOperations setOperation = redisTemplate.boundSetOps(key);
        return setOperation.add(value);

    }


    /**
     * 获得缓存的set
     *
     * @param key
     * @return
     */
    public <T> Set<T> getCacheSet(final String key)
    {
        return redisTemplate.opsForSet().members(key);
    }



    /**
     * 判断set中是否存在value
     * @param key
     * @param value
     * @return
     */
    public Boolean isSetMember(final String key,final Object value){
       return redisTemplate.boundSetOps(key).isMember(value);
    }


    /**
     * 查询set中的size
     * @param key
     * @return
     */
    public Long getSetSize(final String key){

        return redisTemplate.opsForSet().size(key);
    }

    /**
     * 从集合中移除value
     * @param key
     * @param value
     * @return 成功移除的个数
     */
    public Long removeSetValue(final String key,final Object ...value){
        return redisTemplate.boundSetOps(key).remove(value);
    }


    /**
     * 根据得分设置set
     * @param key
     * @param value
     * @param score
     */
    public void setCacheZSet(String key,Object value,long score){
        redisTemplate.opsForZSet().add(key, value, (double) score);
    }

    /**
     * zSet增量
     * @param key
     * @param value
     * @param score
     */
    public void incrementCacheZSet(String key,Object value,long score){
        redisTemplate.opsForZSet().incrementScore(key,value,score);
    }

    /**
     * 根据得分设置set
     * @param key
     * @param value
     * @param score
     */
    public void setCacheZSet(String key,String value,long score){
        redisTemplate.opsForZSet().add(key, value, (double) score);
    }

    /**
     *
     * @param key
     * @param tuples
     * @param <T>
     */
    public <T> void setCacheZSet(String key,Set<ZSetOperations.TypedTuple<T>> tuples){
        redisTemplate.opsForZSet().add(key,tuples);
    }

    /**
     * 获取分页 /按照分数从大到小排序的元素
     * @param key
     * @param pageNum
     * @param pageSize
     * @param <T>
     * @return
     */
    public <T> Set<T> getCacheZSet(String key,Integer pageNum,Integer pageSize){
        int startIndex = (pageNum - 1) * pageSize;

        if (startIndex < 0) {
            startIndex = 0;
        }
        int endIndex = pageNum * pageSize - 1;
        // 2,5   5 9
        // 获取 Sorted Set 元素总数
        long total = redisTemplate.opsForZSet().zCard(key);
        if(startIndex >= total)
            return new HashSet<>();
        if (endIndex >= total) {
            endIndex = (int) total - 1;
        }

        return redisTemplate.opsForZSet().reverseRange(key, startIndex, endIndex);
    }


    public <T> Set<T> getCacheZSet(String key){

        return redisTemplate.opsForZSet().reverseRange(key,0,-1);
    }

    /**
     *
     * @param zkey
     * @param key
     * @return 排行（从0开，大的在前）不存在返回null
     */
    public Long getCacheZSetByKey(String zkey,String key){

        return redisTemplate.opsForZSet().reverseRank(zkey, key);
    }

    /**
     * 查出来全部带分数
     * @param key
     * @return
     */
    public <T> Set<ZSetOperations.TypedTuple<T>> getCacheZSetWithScore(String key){

        return redisTemplate.opsForZSet().rangeWithScores(key,0,-1);
    }
    /**
     * 查询ZSet中的size
     * @param key
     * @return
     */
    public Long getZSetSize(final String key){

        return redisTemplate.opsForZSet().size(key);
    }
    /**
     * 查询ZSet中的分数比k大的数量
     * @param key
     * @return
     */
    public Long getZSetCount(final String key,long k){

        return redisTemplate.opsForZSet().count(key, k, Double.POSITIVE_INFINITY);
    }

    /**
     * 移除zset的value
     * @param key
     * @param value
     */
    public void removeZSet(String key,String value){
        redisTemplate.opsForZSet().remove(key,value);
    }

    /**
     * 判断ZSet是否存在value
     * @param key
     * @param value
     */
    public Boolean isZSetMember(String key,String value){
        Double score = redisTemplate.opsForZSet().score(key, value);
        if(score==null)
            return false;
        return true;
    }

//    -------------------------------map------------------


    /**
     * 缓存Map
     *
     * @param key
     * @param dataMap
     */
    public <T> void setCacheMap(final String key, final Map<String, T> dataMap)
    {
        if (dataMap != null) {
            redisTemplate.opsForHash().putAll(key, dataMap);
        }
    }

    public <T> void setCacheMap(final String key, String hashKey,Object value)
    {
        redisTemplate.opsForHash().put(key,hashKey,value);
    }


    /**
     * 获得缓存的Map
     *
     * @param key
     * @return
     */
    public <K,V> Map<K, V> getCacheMap(final String key)
    {
        return redisTemplate.opsForHash().entries(key);
    }

    /**
     * 往Hash中存入数据
     *
     * @param key Redis键
     * @param hKey Hash键
     * @param value 值
     */
    public <T> void setCacheMapValue(final String key, final String hKey, final T value)
    {
        redisTemplate.opsForHash().put(key, hKey, value);
    }

    /**
     * 获取Hash中的数据
     *
     * @param key Redis键
     * @param hKey Hash键
     * @return Hash中的对象
     */
    public <T> T getCacheMapValue(final String key, final String hKey)
    {
        HashOperations<String, String, T> opsForHash = redisTemplate.opsForHash();
        return opsForHash.get(key, hKey);
    }

    /**
     * 删除Hash中的数据
     *
     * @param key
     * @param hkey
     */
    public void delCacheMapValue(final String key, final String hkey)
    {
        HashOperations hashOperations = redisTemplate.opsForHash();
        hashOperations.delete(key, hkey);
    }

    /**
     * 获取多个Hash中的数据
     *
     * @param key Redis键
     * @param hKeys Hash键集合
     * @return Hash对象集合
     */
    public <T> List<T> getMultiCacheMapValue(final String key, final Collection<Object> hKeys)
    {
        return redisTemplate.opsForHash().multiGet(key, hKeys);
    }

    /**
     * 获得缓存的基本对象列表
     *
     * @param pattern 字符串前缀
     * @return 对象列表
     */
    public Set<String> keys(final String pattern)
    {
        return redisTemplate.keys(pattern.concat("*"));
    }

    // redisTemplate.opsForHash().putIfAbsent()


    /**
     * 如果key存在hashKey插入失败
     * @param key
     * @param hashKey
     * @param value
     * @return
     */
    public Boolean mapPutIfAbsent(String key,String hashKey,Object value){
        return redisTemplate.opsForHash().putIfAbsent(key,hashKey,value);
    }

    /**
     * 判断map的key是否存在
     * @param key
     * @param hashKey
     * @return
     */
    public Boolean mapKeyExit(String key,String hashKey){
        return redisTemplate.opsForHash().hasKey(key,hashKey);
    }

    /**
     *
     * @param key
     * @param hashKey
     * @param value
     * @return
     */
    public long mapIncrement(String key,String hashKey,long value){
        return redisTemplate.opsForHash().increment(key,hashKey,value);
    }

    /**
     *
     * @param key
     * @param hashKey
     * @return
     */
    public long removeMapKey(String key,String hashKey){
        return redisTemplate.opsForHash().delete(key,hashKey);
    }

    /**
     * 根据前缀获取所有的key
     * 例如：pro_*
     */
    public Set<String> getListKey(String prefix) {
        Set<String> keys = redisTemplate.keys(prefix.concat("*"));
        return keys;
    }


    /**
     * 解决缓存穿透,没有解决缓存击穿
     * 缓存穿透是指缓存和数据库中都没有的数据，而用户不断发起请求，
     * 如发起为id为“-1”的数据或id为特别大不存在的数据。这时的用户很可能是攻击者，攻击会导致数据库压力过大。
     * @param keyPrefix key前缀
     * @param id key后缀
     * @param type 返回类型
     * @param dbFallback 查询回调
     * @param time 缓存时间
     * @param unit 缓存单位
     * @param <R> 返回类型
     * @param <ID> 操作数据类型
     * @return
     */
    public <R,ID> R queryWithPassThrough(
            String keyPrefix, ID id, Class<R> type, Function<ID, R> dbFallback, Long time, TimeUnit unit){
        String key = keyPrefix + id;
        // 1.从redis缓存查
        String json = JSONUtil.toJsonStr(redisTemplate.opsForValue().get(key));
        // 2.判断是否存在
        if (StrUtil.isNotBlank(json)) {
            // 3.存在，直接返回
            return JSONUtil.toBean(json, type);
        }
        // 判断命中的是否是空值 因为如果数据库也不存在就设redis为"" 预防缓存穿透
        if (json.equals("")) {
            // 返回一个错误信息
            return null;
        }

        // 4.不存在，根据id查询数据库
        R r = dbFallback.apply(id);
        // 5.不存在，返回错误
        if (r == null) {
            // 将空值写入redis  预防缓存穿透
            redisTemplate.opsForValue().set(key, "", CACHE_NULL_TTL, TimeUnit.MINUTES);
            // 返回错误信息
            return null;
        }
        // 6.存在，写入redis
        this.set(key, r, time, unit);
        return r;
    }

    public void set(String key, Object value, Long time, TimeUnit unit) {
        redisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(value), time, unit);
    }

    /**
     * 互斥锁解决缓存击穿
     * 缓存击穿：redis无数据，数据库有数据，大量请求打到数据库
     * @param keyPrefix
     * @param id
     * @param type
     * @param dbFallback
     * @param time
     * @param unit
     * @param <R>
     * @param <ID>
     * @return
     */
    public <R, ID> R queryWithMutex(
            String keyPrefix, ID id, Class<R> type, Function<ID, R> dbFallback, Long time, TimeUnit unit) {
        String key = keyPrefix + id;
        // 1.从redis查询缓存
        String shopJson = JSONUtil.toJsonStr(redisTemplate.opsForValue().get(key));
        // 2.判断是否存在
        if (StrUtil.isNotBlank(shopJson)) {
            // 3.存在，直接返回
            return JSONUtil.toBean(shopJson, type);
        }
        // 判断命中的是否是空值
        if (shopJson != null) {
            // 返回一个错误信息
            return null;
        }
        //——————————redis无数据————————//

        // 4.实现缓存重建
        // 4.1.获取互斥锁
        String lockKey = LOCK_KEY + id;
        R r = null;
        try {
            // 4.2.判断是否获取成功
            if (!tryLock(lockKey)) {
                // 4.3.获取锁失败，休眠并重试
                Thread.sleep(50);
                return queryWithMutex(keyPrefix, id, type, dbFallback, time, unit);
            }
            // 4.4.获取锁成功，根据id查询数据库
            r = dbFallback.apply(id);
            // 5.不存在，返回错误
            if (r == null) {
                // 将空值写入redis
                redisTemplate.opsForValue().set(key, "", CACHE_NULL_TTL, TimeUnit.MINUTES);
                // 返回错误信息
                return null;
            }
            // 6.存在，写入redis
            this.set(key, r, time, unit);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }finally {
            // 7.释放锁
            unlock(lockKey);
        }
        // 8.返回
        return r;
    }

    /**
     * 使用redisson互斥锁解决缓存击穿
     * 缓存击穿：redis无数据，数据库有数据，大量请求打到数据库
     * @param keyPrefix
     * @param id
     * @param type
     * @param dbFallback
     * @param time
     * @param unit
     * @param <R>
     * @param <ID>
     * @return
     */
//    public <R, ID> R queryWithMutexByRedisson(
//            String keyPrefix, ID id, Class<R> type, Function<ID, R> dbFallback, Long time, TimeUnit unit) {
//        String key = keyPrefix + id;
//        // 1.从redis查询缓存
//        String shopJson = JSONUtil.toJsonStr(redisTemplate.opsForValue().get(key));
//        // 2.判断是否存在
//        if (StrUtil.isNotBlank(shopJson)) {
//            // 3.存在，直接返回
//            return JSONUtil.toBean(shopJson, type);
//        }
//
//        if (shopJson != null) {
//
//            return null;
//        }
//        //——————————redis无数据————————//
//
//
//        //带可重入加锁机制，watch dog自动延期机制，锁互斥机制
//        RLock lock = redissonClient.getLock(LOCK_KEY + key);
//        R r = null;
//        try {
//            if (lock.tryLock(10, TimeUnit.SECONDS)) {
//
//                // 1.重新从redis查询缓存
//                 shopJson = JSONUtil.toJsonStr(redisTemplate.opsForValue().get(key));
//                // 2.判断是否存在
//                if (StrUtil.isNotBlank(shopJson)) {
//                    // 3.存在，直接返回
//                    return JSONUtil.toBean(shopJson, type);
//                }
//
//                // 4.4.获取锁成功，根据id查询数据库
//                r = dbFallback.apply(id);
//                // 5.不存在，返回错误
//                if (r == null) {
//                    // 将空值写入redis
//                    redisTemplate.opsForValue().set(key, "", CACHE_NULL_TTL, TimeUnit.MINUTES);
//                    // 返回错误信息
//                    return null;
//                }
//                // 6.存在，写入redis
//               // this.setCacheSet(key,r);
//                this.set(key, r, time, unit);
//            }
//        } catch (InterruptedException e) {
//            throw new RuntimeException(e);
//        }finally {
//            // 7.释放锁
//            lock.unlock();
//        }
//        // 8.返回
//        return r;
//    }

    /**
     * setIfAbsent：原子性上锁
     * 无看门狗，不能确保线程安全，如果业务时间>锁时间会导致多线程进入并且释放了别的线程的锁。
     * @param key
     * @return
     */
    private boolean tryLock(String key) {
        Boolean flag = redisTemplate.opsForValue().setIfAbsent(key, "1", 5, TimeUnit.SECONDS);
        if(flag==null)
            return false;
        return BooleanUtil.isTrue(flag);
    }



    private void unlock(String key) {
        redisTemplate.delete(key);
    }


    /**
     * 带逻辑过期的缓存
     * @param key
     * @param value
     * @param time
     * @param unit
     */
    public void setWithLogicalExpire(String key, Object value, Long time, TimeUnit unit) {
        // 设置逻辑过期
        RedisData redisData = new RedisData();
        redisData.setData(value);
        redisData.setExpireTime(LocalDateTime.now().plusSeconds(unit.toSeconds(time)));
        // 写入Redis
        redisTemplate.opsForValue().set(key, redisData);
    }


//    /**
//     *
//     * @param key 参数表示目标有序集合的键名
//     * @param max 表示最大 Score 值，即只包含分数值小于等于 max 的成员
//     * @param offset  表示查询结果的偏移量
//     * @param pageSize 即获取的个数
//     * @return
//     */
//    public Set<ZSetOperations.TypedTuple<String>> getCacheZSetByScoreWithScores(
//            String key,long max, long offset, long pageSize) {
//        return redisTemplate.opsForZSet().reverseRangeByScoreWithScores(key,0,max,offset,pageSize);
//    } //54321

    /**
     *
     * @param key 参数表示目标有序集合的键名
     * @param max 表示最大 Score 值，即只包含分数值小于等于 max 的成员
     * @param offset  表示查询结果的偏移量
     * @param pageSize 即获取的个数
     * @return
     */
    public Set<ZSetOperations.TypedTuple<Object>> getCacheZSetByScoreWithScores(
            String key,long max, long offset, long pageSize) {

        return redisTemplate.opsForZSet().reverseRangeByScoreWithScores(key,0,max,offset,pageSize);
    } //54321




    @Data
    private class RedisData {
        //逻辑过期时间
        private LocalDateTime expireTime;
        private Object data;
    }


    /**
     * 判断某个key是否存在
     * @param key
     * @return
     */
    public boolean exist(String key){

        Boolean flag = false;
        try{
            flag = redisTemplate.hasKey(key);
            return flag;
        }catch(Exception e){
            e.printStackTrace();
        }
        return flag;
    }


    /**
     * 从缓存拿数据，没有就通过obj重构缓存,返回R
     * @param key 缓存key
     * @param queryByRedis 从redis查询数据
     * @param buildRedis 构建缓存
     * @param <R>
     * @return
     */
    public <R> R queryWithMutexAndBuild(
            String key,Function<String, R> queryByRedis, Function<String,R> buildRedis)
    {
       //缓存存在
        if(exist(key)){
            return queryByRedis.apply(key);
        }

        //缓存不存在
        String lockKey = LOCK_KEY + key;
        RLock lock = redissonClient.getLock(lockKey);
        //加锁防止大量数据打到数据库 缓存击穿
        lock.lock();// 加锁，拿不到锁会一直自旋等待
        //redisson加的锁默认过期时长是30s,没过10s续一次
        //redisson不会死锁，因为有过期时间
        try {  // 获取锁成功，重构缓存
            if (!exist(key)) { // double check
                //重构缓存并且返回
                return buildRedis.apply(key);
            }
            return queryByRedis.apply(key);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }finally {
            // 7.释放锁
            lock.unlock();
        }
    }


    /**
     * 消费缓存，没有就通过obj重构缓存,返回R
     * @param key 缓存key
     * @param queryByRedis 从redis查询数据
     * @param buildRedis 构建缓存
     * @return
     */
    public void consumerWithMutexAndBuild(
            String key,Consumer<String> queryByRedis, Consumer<String> buildRedis)  {
        if(exist(key)) //缓存存在
        {
            queryByRedis.accept(key);
            return;
        }

        String lockKey = LOCK_KEY + key;
        RLock lock = redissonClient.getLock(lockKey);

        //加锁防止大量数据打到数据库 缓存击穿
        lock.lock();// 加锁，拿不到锁会一直自旋等待
        //redisson加的锁默认过期时长是30s,没过10s续一次
        //redisson不会死锁，因为有过期时间
        try {  // 获取锁成功，重构缓存
            if (!exist(key)) { // double check
                //重构缓存
                buildRedis.accept(key);
            }
            queryByRedis.accept(key);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }finally {
            // 7.释放锁
            lock.unlock();
        }
    }




}
